package models

import (
	"fmt"
	"reflect"
	"regexp"
	"strings"
	"xorm.io/xorm"
	//"xorm.io/core"
	"zorm/consts"
	//"zorm/errors"
	"unsafe"
)

type DB struct {
	RealDB                 interface{}
	Sessions               *Session
	DBType                 string
	Migrate                *Migrator
	rowsEffect             int64
	rowsEffectWithoutLimit int64
	err                    error
}

func (d *DB) Migrator() *Migrator {
	return d.Migrate
}

func (d *DB) Error() error {
	return d.err
}

func (d *DB) AutoMigrate(values ...interface{}) error {
	var (
		err error
	)
	if d.DBType == consts.SqlDatabaseStr {
		err = d.RealDB.(*xorm.Engine).Sync2(values...)
		//err = m.RealDB.(*xorm.Engine).Sync2(values...)
	} else {

	}
	if err != nil {
		fmt.Println(err)
		return err
	}
	return nil
}

func (d *DB) Find(value interface{}, conds ...interface{}) *DB {
	var (
		//isSlice bool
		rowsEffectWithoutLimit int64
		rowsEffect             int64
		xormSession            *xorm.Session
		countSession           *xorm.Session
		countValue             interface{}
		err                    error
		t                      reflect.Type
		k                      reflect.Kind
	)
	if d.DBType == consts.SqlDatabaseStr {
		t = reflect.TypeOf(value)
		k = t.Elem().Kind()
		if k == reflect.Slice {
			countValue = reflect.MakeSlice(t, 0, 0)
			xormSession, countSession = d.xormConcatSessionDB()
			_ = countSession.Find(countValue, conds...)
			rowsEffectWithoutLimit = *(*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(reflect.ValueOf(countValue).Pointer())) + uintptr(8)))
			err = xormSession.Find(value, conds...)
			rowsEffect = *(*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(reflect.ValueOf(value).Pointer())) + uintptr(8)))
			//_ = countSession.Find(value, conds...)
			//isSlice = true
		} else if k == reflect.Struct {
			_, err = xormSession.Get(value)
			rowsEffect = 1
			rowsEffectWithoutLimit = 1
			//err = d.RealDB.(*xorm.Engine).Find(h,conds...)
		} else {

		}
	} else {
		//todo
	}
	if err != nil {
		fmt.Println(err)
		d.err = err
	} else {

	}
	d.rowsEffect = rowsEffect
	d.rowsEffectWithoutLimit = rowsEffectWithoutLimit
	return d
}

//func (d *DB) First(value interface{}, conds ...interface{}) *DB {
//	var (
//		//isSlice bool
//		err error
//		t   reflect.Type
//		k   reflect.Kind
//	)
//	if d.DBType == consts.SqlDatabaseStr {
//		d.appendSession(d.newSession("order", "id_asc", nil, nil, d.Sessions))
//		t = reflect.TypeOf(value)
//		k = t.Elem().Kind()
//		if k == reflect.Slice {
//			err = errors.CantBeSlicePtrError
//			//isSlice = true
//		} else if k == reflect.Struct {
//			_, err = d.xormConcatSessionDB().Get(value)
//			//err = d.RealDB.(*xorm.Engine).Find(h,conds...)
//		} else {
//
//		}
//	} else {
//		//todo
//	}
//	if err != nil {
//		fmt.Println(err)
//		d.err = err
//	} else {
//
//	}
//	return d
//}
//
//func (d *DB) Last(value interface{}, conds ...interface{}) *DB {
//	var (
//		//isSlice bool
//		err error
//		t   reflect.Type
//		k   reflect.Kind
//	)
//	if d.DBType == consts.SqlDatabaseStr {
//		d.appendSession(d.newSession("order", "id_desc", nil, nil, d.Sessions))
//		t = reflect.TypeOf(value)
//		k = t.Elem().Kind()
//		if k == reflect.Slice {
//			err = errors.CantBeSlicePtrError
//			//isSlice = true
//		} else if k == reflect.Struct {
//			_, err = d.xormConcatSessionDB().Get(value)
//			//err = d.RealDB.(*xorm.Engine).Find(h,conds...)
//		} else {
//
//		}
//	} else {
//		//todo
//	}
//	if err != nil {
//		fmt.Println(err)
//		d.err = err
//	} else {
//
//	}
//	return d
//}

func (d *DB) Where(query interface{}, args ...interface{}) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		query = d.xormSuitOracle(query.(string))
		d.appendSession(d.newSession("where", query, args, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Model(obj interface{}) *DB {
	var (
		nd        *DB
		tableName string
	)
	if d.DBType == consts.SqlDatabaseStr {
		tableName = d.Obj2Table(obj)
		d.appendSession(d.newSession("table", tableName, nil, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Table(tableName string) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		d.appendSession(d.newSession("table", tableName, nil, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Limit(limit int64) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		d.appendSession(d.newSession("limit", limit, nil, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Offset(offset int64) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		d.appendSession(d.newSession("offset", offset, nil, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Order(value interface{}) *DB {
	var (
		nd        *DB
		orderBy   string
		orderType string
		//args []string
	)
	if d.DBType == consts.SqlDatabaseStr {
		orderBy, orderType = d.xormParseOrder(value.(string))
		//args = strings.Split(orderBy,",")
		//d.appendSession(d.newSession("order", orderBy , nil, nil, d.Sessions))
		d.appendSession(d.newSession("order", orderBy+"_"+orderType, nil, nil, d.Sessions))
		//if orderType == "asc"{
		//	d.appendSession(d.newSession("asc", orderType, nil, nil, d.Sessions))
		//}else if orderType == "desc"{
		//	d.appendSession(d.newSession("desc", orderType, nil, nil, d.Sessions))
		//}else{
		//	fmt.Println("order出错")
		//}
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Join(query interface{}, args ...interface{}) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		d.appendSession(d.newSession("join", query, args, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Not(query interface{}, args ...interface{}) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		d.appendSession(d.newSession("not", query, args, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Or(query interface{}, args ...interface{}) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		d.appendSession(d.newSession("or", query, args, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Select(query interface{}, args ...interface{}) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		d.appendSession(d.newSession("select", query, args, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Group(name string) *DB {
	var (
		nd *DB
	)
	if d.DBType == consts.SqlDatabaseStr {
		d.appendSession(d.newSession("group", name, nil, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Having(query interface{}, args ...interface{}) *DB {
	var (
		nd          *DB
		queryString string
	)
	if d.DBType == consts.SqlDatabaseStr {
		queryString = query.(string)
		for _, arg := range args {
			queryString = strings.Replace(queryString, "?", fmt.Sprintf("'%v'", arg), 1)
		}
		d.appendSession(d.newSession("having", queryString, nil, nil, d.Sessions))
		nd = d.newDB()
	} else {
		//todo
	}
	return nd
}

func (d *DB) Count(count *int64) *DB {
	var (
		nd *DB
	)
	*count = d.rowsEffectWithoutLimit
	nd = d.newDB()
	return nd
}

func (d *DB) RowsEffect(count *int64) *DB {
	var (
		nd *DB
	)
	*count = d.rowsEffect
	nd = d.newDB()
	return nd
}

//增删改
func (d *DB) Create(value interface{}) *DB {
	var (
		nd  *DB
		err error
	)
	nd = d.newDB()
	if d.DBType == consts.SqlDatabaseStr {
		_, err = d.xormGetDB().InsertOne(value)
		if err != nil {
			nd.err = err
		}
	} else {
		//todo
	}
	return nd
}

func (d *DB) Updates(value interface{}) *DB {
	var (
		nd          *DB
		rowsEffect  int64
		xormSession *xorm.Session
		err         error
	)
	nd = d.newDB()
	if d.DBType == consts.SqlDatabaseStr {
		xormSession, _ = d.xormConcatSessionDB()
		rowsEffect, err = xormSession.Update(value)
		if err != nil {
			nd.err = err
		}
	} else {
		//todo
	}
	nd.rowsEffect = rowsEffect
	nd.rowsEffectWithoutLimit = rowsEffect
	return nd
}

func (d *DB) Delete(value interface{}, conditions ...interface{}) *DB {
	var (
		nd          *DB
		rowsEffect  int64
		xormSession *xorm.Session
		err         error
	)
	nd = d.newDB()
	if d.DBType == consts.SqlDatabaseStr {
		xormSession, _ = d.xormConcatSessionDB()
		rowsEffect, err = xormSession.Delete(value)
		if err != nil {
			nd.err = err
		}
	} else {
		//todo
	}
	nd.rowsEffect = rowsEffect
	nd.rowsEffectWithoutLimit = rowsEffect
	return nd
}

//对外工具方法
func (d *DB) Obj2Table(obj interface{}) string {
	var (
		tableName,
		objName string
	)
	if d.DBType == consts.SqlDatabaseStr {
		objName = d.xormGetObjName(obj)
		tableName = d.RealDB.(*xorm.Engine).GetTableMapper().Obj2Table(objName)
		return tableName
	} else {
		return ""
	}
}

func (d *DB) Table2Obj(table string) string {
	var (
		objName string
	)
	if d.DBType == consts.SqlDatabaseStr {
		objName = d.RealDB.(*xorm.Engine).GetTableMapper().Table2Obj(table)
		return objName
	} else {
		return ""
	}
}

func (d *DB) Obj2Column(obj string) string {
	var (
		columnName string
	)
	if d.DBType == consts.SqlDatabaseStr {
		columnName = d.RealDB.(*xorm.Engine).GetColumnMapper().Obj2Table(obj)
		return columnName
	} else {
		return ""
	}
}

func (d *DB) Column2Obj(column string) string {
	var (
		objName string
	)
	if d.DBType == consts.SqlDatabaseStr {
		objName = d.RealDB.(*xorm.Engine).GetColumnMapper().Table2Obj(column)
		return objName
	} else {
		return ""
	}
}

//对内工具方法
func (d *DB) newDB() (tx *DB) {
	var (
		nd = new(DB)
	)
	nd.DBType = d.DBType
	nd.err = nil
	nd.Migrate = nil
	nd.RealDB = d.RealDB
	nd.Sessions = d.Sessions
	return nd
}

func (d *DB) newSession(args ...interface{}) *Session {
	var (
		s = new(Session)
	)
	s.OperaType = args[0].(string)
	s.Query = args[1]
	if args[2] == nil {
		s.Args = nil
	} else {
		s.Args = args[2].([]interface{})
	}
	if args[3] == nil {
		s.Objs = nil
	} else {
		s.Objs = args[3].([]interface{})
	}
	s.Previous = args[4].(*Session)
	s.Suffix = nil
	return s
}

func (d *DB) appendSession(s *Session) {
	var (
		xormSession = d.Sessions
	)
	for {
		if xormSession.Suffix == nil {
			xormSession.Suffix = s
			return
		} else {
			xormSession = xormSession.Suffix
		}
	}
}

func (d *DB) xormSuitOracle(baseSql string) string {
	var (
		result     string
		regex      *regexp.Regexp
		splitSlice []string
	)
	regex, _ = regexp.Compile("\\s+")
	splitSlice = regex.Split(baseSql, -1)
	for i := 0; i < len(splitSlice)-1; i++ {
		switch splitSlice[i+1] {
		case ">", "<", ">=", "<=", "=", "==", "<>", "!=":
			result = result + fmt.Sprintf(`"%s" `, splitSlice[i])
		default:
			result = result + fmt.Sprintf(`%s `, splitSlice[i])
		}
	}
	result = result + splitSlice[len(splitSlice)-1]
	return result
}

func (d *DB) xormGetObjName(obj interface{}) string {
	var (
		t reflect.Type
	)
	t = reflect.TypeOf(obj)
	switch t.Kind() {
	case reflect.Ptr:
		return t.Elem().Name()
	default:
		return t.Name()
	}
}

func (d *DB) xormConcatSessionDB() (*xorm.Session, *xorm.Session) {
	var (
		xormSession        = d.xormGetDB().NewSession()
		countSession       = d.xormGetDB().NewSession()
		zormSession        = d.Sessions
		offset       int64 = -1
		limit        int64 = -1
		join         string
		table        interface{}
		condition    string
		ascSlice     []string
		descSlice    []string
	)
	for {
		switch zormSession.OperaType {
		case "where":
			xormSession = xormSession.Where(zormSession.Query, zormSession.Args...)
		case "not":
			xormSession = xormSession.Where(fmt.Sprintf("not (%s)", zormSession.Query), zormSession.Args...)
		case "or":
			xormSession = xormSession.Or(zormSession.Query, zormSession.Args...)
		case "select":
			xormSession = xormSession.Select(zormSession.Query.(string))
		case "limit":
			limit = zormSession.Query.(int64)
		case "offset":
			offset = zormSession.Query.(int64)
		case "group":
			xormSession = xormSession.GroupBy(zormSession.Query.(string))
		case "having":
			xormSession = xormSession.Having(zormSession.Query.(string))
		case "distinct":
			xormSession = xormSession.Distinct(d.xormConvertSlice(zormSession.Objs, "string").([]string)...)
		case "join":
			join, table, condition = d.xormParseJoin(zormSession.Query.(string))
			xormSession = xormSession.Join(join, table, condition, zormSession.Args...)
		case "table":
			xormSession = xormSession.Table(zormSession.Query.(string))
		case "order":
			orderArgs := strings.Split(zormSession.Query.(string), "_")
			if orderArgs[1] == "asc" {
				ascSlice = append(ascSlice, orderArgs[0])
			} else {
				descSlice = append(descSlice, orderArgs[0])
			}
			//xormSession = xormSession.OrderBy(zormSession.Query.(string))
			//case "asc":
			//	xormSession = xormSession.Asc(zormSession.Query.([]string)...)
			//case "desc":
			//	xormSession = xormSession.Desc(zormSession.Query.([]string)...)
		}
		if zormSession.Suffix == nil {
			countSession = xormSession
			if offset != -1 && limit != -1 {
				xormSession = xormSession.Limit(int(limit), int(offset))
			}
			if len(ascSlice) != 0 {
				xormSession = xormSession.Asc(ascSlice...)
			}
			if len(descSlice) != 0 {
				xormSession = xormSession.Desc(descSlice...)
			}
			return xormSession, countSession
		} else {
			zormSession = zormSession.Suffix
		}
	}
}

func (d *DB) xormGetDB() *xorm.Engine {
	return d.RealDB.(*xorm.Engine)
}

func (d *DB) xormConvertSlice(s []interface{}, t string) interface{} {
	var (
		i  interface{}
		ss []string
	)
	for _, i = range s {
		switch t {
		case "string":
			ss = append(ss, i.(string))
		}
	}
	switch t {
	case "string":
		return ss
	default:
		return ss
	}
}

func (d *DB) xormParseJoin(s string) (string, interface{}, string) {
	var (
		regex      *regexp.Regexp
		splitSlice []string
		join       string
		table      interface{}
		condition  string
	)
	s = strings.ToLower(s)
	regex, _ = regexp.Compile("\\s+on\\s+")
	splitSlice = regex.Split(s, 1)
	condition = splitSlice[1]
	regex, _ = regexp.Compile("\\s+")
	splitSlice = regex.Split(splitSlice[0], -1)
	if len(splitSlice) == 4 {
		join = splitSlice[0]
		table = []string{splitSlice[1], splitSlice[3]}
	} else if len(splitSlice) == 2 {
		join = splitSlice[0]
		table = splitSlice[1]
	} else if len(splitSlice) == 5 {
		join = splitSlice[0] + " " + splitSlice[1]
		table = []string{splitSlice[2], splitSlice[4]}
	} else {
		fmt.Println("splitSlice长度不对")
	}
	if join == "join" {
		join = "INNER"
	} else if join == "left join" {
		join = "LEFT OUTER"
	} else if join == "cross join" {
		join = "CROSS"
	} else {
		fmt.Println("不支持的join类型")
	}
	return join, table, condition
}

func (d *DB) xormParseOrder(s string) (string, string) {
	var (
		regex      *regexp.Regexp
		splitSlice []string
		orderBy    string
		orderType  string
	)
	s = strings.ToLower(s)
	regex, _ = regexp.Compile("\\s+")
	splitSlice = regex.Split(s, -1)
	orderBy = splitSlice[0]
	orderType = splitSlice[1]
	return orderBy, orderType
}
